/*
 * Database.java
 * Author: Thach Nguyen
 * Version 5/21/2011 Started Code
 * Version 5/24/2011 
 */
package data;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import parsers.ClassParser;
import parsers.TimeSlotParser;
import parsers.UserParser;
import user.User;
import user.UserType;

/**
 * Represents a database of all users and their preferences.
 * @author Thach Nguyen
 * @version 6/1/2011
 */
public class Database
{
	/**
	 * This variable represents the number of Top student classes to consider as input.
	 */
	public static final double STUDENT_CUT_OFF_PERCENTAGE = 0.05;
	
	private static final String CLASS_SET_SUFFIX = "classes.txt";
	private static final String TIME_SET_SUFFIX = "TimeSlots.txt";
	private static final String USER_PREF_SUFFIX = "users.txt";
	
	private List<User> myStudents;
	private List<User> myTeachers;
	private List<User> myAdvisers;
	private List<User> mySchedulers;
	private Map<Integer, User> myUsers;
	
	private final File myUserFile;
	
	/**
	 * Constructs a Database and attempts to fill it using files located at theDirectory.
	 * If the files cannot be located, this will attempt to create them at theDirectory.
	 * 
	 * @param theDirectory the Path to the desired directory.
	 * @throws FileNotFoundException if the file exists but is a directory rather than a regular file,
	 * does not exist but cannot be created, or cannot be opened for any other reason
	 */
	public Database(String theDirectory) throws FileNotFoundException
	{
		myStudents = new ArrayList<User>();
		myTeachers = new ArrayList<User>();
		myAdvisers = new ArrayList<User>();
		mySchedulers = new ArrayList<User>();
		myUsers = new HashMap<Integer, User>();
		myUserFile = new File(theDirectory + USER_PREF_SUFFIX);
		loadFiles(theDirectory);
	}
	
	/**
	 * Attempts to parse files from theDirectory.
	 * @param theDirectory the file path of the directory where the files can be located.
	 * @throws FileNotFoundException if the file exists but is a directory rather than a regular file,
	 * does not exist but cannot be created, or cannot be opened for any other reason
	 */
	private void loadFiles(String theDirectory) throws FileNotFoundException {
		final File classFile = new File(theDirectory + CLASS_SET_SUFFIX);
		final File timeFile = new File(theDirectory + TIME_SET_SUFFIX);
		UserParser userParser = null;
		try {
			userParser = new UserParser(myUserFile, parseClasses(classFile), parseTimes(timeFile));
		} catch (FileNotFoundException e) {
			System.err.println("FileNotFound");
			FileOutputStream temp = new FileOutputStream(myUserFile); // If The file was not found, make a file.
			try {														// Throws FileNotFoundException
				temp.close();
			} catch (IOException e1) {
				e1.printStackTrace(); // Should never reach here.
			}
			userParser = new UserParser(myUserFile, parseClasses(classFile), parseTimes(timeFile));
		}
		
		for (User user: userParser.getUserList()) {
			addUser(user);
		}
	}
	
	/**
	 * @throws FileNotFoundException if the file exists but is a directory rather than a regular file,
	 * does not exist but cannot be created, or cannot be opened for any other reason
	 * 
	 */
	private Set<ClassPreference> parseClasses(final File theClassFile) throws FileNotFoundException {
		ClassParser classParser = null;
		try {
			classParser = new ClassParser(theClassFile);
		} catch (FileNotFoundException e) {
			System.err.println("FileNotFound");
			FileOutputStream temp = new FileOutputStream(theClassFile); // If The file was not found, make a file.
			try {														// Throws FileNotFoundException
				temp.close();
			} catch (IOException e1) {
				e1.printStackTrace(); // Should never reach here.
			}
			classParser = new ClassParser(theClassFile);
		}
		return classParser.getClasses(); // List of Classes parsed from file
	}
	
	/**
	 * @throws FileNotFoundException if the file exists but is a directory rather than a regular file,
	 * does not exist but cannot be created, or cannot be opened for any other reason
	 * 
	 */
	private Set<TimeSlot> parseTimes(final File theTimeFile) throws FileNotFoundException {
		TimeSlotParser timeParser = null;
		try {
			timeParser = new TimeSlotParser(theTimeFile);
		} catch (FileNotFoundException e) {
			System.err.println("FileNotFound");
			FileOutputStream temp = new FileOutputStream(theTimeFile); // If The file was not found, make a file.
			try {														// Throws FileNotFoundException
				temp.close();
			} catch (IOException e1) {
				e1.printStackTrace(); // Should never reach here.
			}
			timeParser = new TimeSlotParser(theTimeFile);
		}
		return timeParser.getTimeSet(); // List of TimeSlots parsed from file
	}
	
	
	public void addUser(final User theUser)
	{
		final List<UserType> types = theUser.getUserType();
		myUsers.put(theUser.getUserID(), theUser);
		if (types.contains(UserType.STUDENT)) {
			myStudents.add(theUser);
		}
		if (types.contains(UserType.TEACHER)) {
			myTeachers.add(theUser);
		}
		if (types.contains(UserType.ADVISER)) {
			myAdvisers.add(theUser);
		}
		if (types.contains(UserType.SCHEDULER))	{
			mySchedulers.add(theUser);
		}
	}
	
	/**
	 * 
	 * @post for all UserTypes in theTypes, the user is added to that list.
	 */
	public void addUser(final int theId, final String theName, final UserType[] theTypes) {
		final User temp = new User(theId, theName, theTypes);
		addUser(temp);
	}
	
	public User getUser(final int theId) {
		return myUsers.get(theId);
	}
	
	public List<User> getStudents()
	{
		return Collections.unmodifiableList(myStudents);
	}

	public List<User> getTeachers() {
		return Collections.unmodifiableList(myTeachers);
	}

	public List<User> getAvisors() {
		return Collections.unmodifiableList(myAdvisers);
	}
	
	/**
	 * Gets the Class Constraints for the students. Gathers them up into a list.
	 * @pre getStudents().size != 0;
	 * @return A list of all classes that were voted for by more than 5% of students.
	 */
	public List<ClassPreference> getStudentClassConstraints() {
		Map<ClassPreference, Integer> studentClasses = new HashMap<ClassPreference, Integer>();
		for (User student: myStudents){
			for (ClassPreference ClassSelection : student.getClasses(UserType.STUDENT)) {

				//if a student has already requested this class
				if (studentClasses.containsKey(ClassSelection)) {
					Integer studentCounter = studentClasses.get(ClassSelection) + 1;
					studentClasses.put(ClassSelection, (studentCounter));
				}
				//this is the first student so far that has requested this class.
				else {
					studentClasses.put(ClassSelection, 1);
				}
			} //end of getting classes loop.
		} //end of getting student info loop.
		
		Set<Entry<ClassPreference, Integer>> classEntries = studentClasses.entrySet();
		//Sort the Map then return top classes.
		return getTopStudentClasses(classEntries);
	}
	
	/**
	 * @return A list of all classes that were voted for by more than 5% of students.
	 */
	private List<ClassPreference> getTopStudentClasses (Set<Entry<ClassPreference, Integer>> theEntries) {
		
		final int cutOff = (int) (myStudents.size() * STUDENT_CUT_OFF_PERCENTAGE);
		List<ClassPreference> topClasses = new ArrayList<ClassPreference>();
		
		for (Entry<ClassPreference, Integer> entry : theEntries) {
			if (entry.getValue() > cutOff) {
				topClasses.add(entry.getKey());
			}
		}
		
		return topClasses;
	}

	/**
	 * Gets the instructor Class preferences, and maps them such that:
	 * Instructor -> List of preferred classes.
	 * 
	 * @param the_instructors a list of instructor Users.
	 */
	public Map<String, User> getTeacherConstraints() {
		Map<String, User> teachers =
			new HashMap<String, User>();

		for (User teacher: myTeachers){
			teachers.put(teacher.getName(), teacher);
		}
		return teachers;
	}
	
	
	/**
	 * Gets the Class Constraints for the students. Gathers them up into a list.
	 * @pre getStudents().size != 0;
	 * @return A list of all TimeSlots that were voted for by more than 5% of students.
	 */
	public List<TimeSlot> getStudentTimeConstraints(){

		Map<TimeSlot, Integer > studentTimes = new HashMap<TimeSlot, Integer>();
		for (User student: myStudents){
			for(TimeSlot courseTime:student.getTimes(UserType.STUDENT)){
				//if a student has already requested this time block
				if(studentTimes.containsKey(courseTime)){
					Integer StudentCounter = studentTimes.get(courseTime) + 1;
					studentTimes.put(courseTime, (StudentCounter));
				}
				//this is the first student so far that has requested this time block.
				else{
					studentTimes.put(courseTime, 1);
				}
			}
			/* end of getting time block loop. */
		}
		/* end of getting student info loop. */
		
		Set<Entry<TimeSlot, Integer>> classEntries = studentTimes.entrySet();
		return getTopStudentTimes(classEntries);
	}
	/* end of Gavin's methods */
	
	
	/**
	 * @return A list of all classes that were voted for by more than 5% of students.
	 */
	private List<TimeSlot> getTopStudentTimes (Set<Entry<TimeSlot, Integer>> theEntries) {
		final int cutOff = (int) (myStudents.size() * STUDENT_CUT_OFF_PERCENTAGE);
		List<TimeSlot> topClasses = new ArrayList<TimeSlot>();
		for (Entry<TimeSlot, Integer> entry : theEntries) {
			if (entry.getValue() > cutOff) {
				topClasses.add(entry.getKey());
			}
		}
		
		return topClasses;
	}
	
	
}
